/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.gui.d3;

import georegression.geometry.GeometryMath_F64;
import georegression.geometry.RotationMatrixGenerator;
import georegression.struct.point.Point2D_F64;
import georegression.struct.point.Point3D_F64;
import georegression.struct.point.Vector3D_F64;
import georegression.struct.se.Se3_F64;
import georegression.transform.se.SePointOps_F64;
import org.ejml.data.DenseMatrix64F;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Draws a sequence polygons in 3D
 *
 * @author Peter Abeles
 */
public class Polygon3DSequenceViewer extends JPanel implements KeyListener, MouseListener,
		MouseMotionListener {

	// the shapes it's drawing
	List<Poly> polygons = new ArrayList<Poly>();

	// transform from world frame to camera frame
	Se3_F64 worldToCamera = new Se3_F64();

	// intrinsic camera calibration
	DenseMatrix64F K;

	// how far it moves in the world frame for each key press
	double stepSize;

	// previous cursor location
	int prevX;
	int prevY;

	public Polygon3DSequenceViewer( ) {
		addKeyListener(this);
		addMouseListener(this);
		addMouseMotionListener(this);
	}

	public DenseMatrix64F getK() {
		return K;
	}

	public void setK(DenseMatrix64F k) {
		K = k;
	}

	public double getStepSize() {
		return stepSize;
	}

	public void setStepSize(double stepSize) {
		this.stepSize = stepSize;
	}

	public void init() {
		SwingUtilities.invokeLater( new Runnable() {
			@Override
			public void run() {
				polygons.clear();
			}
		});
	}

	/**
	 * Adds a polygon to the viewer.  GUI Thread safe.
	 *
	 * @param polygon shape being added
	 */
	public void add( Color color , Point3D_F64... polygon ) {

		final Poly p = new Poly(polygon.length,color);

		for( int i = 0; i < polygon.length; i++ )
			p.pts[i] = polygon[i].copy();



		SwingUtilities.invokeLater( new Runnable() {
			@Override
			public void run() {
				polygons.add( p );
			}
		});
	}

	/**
	 * Adds a polygon to the viewer.  GUI Thread safe.
	 *
	 * @param polygon shape being added
	 */
	public void add( Point3D_F64... polygon ) {

		final Poly p = new Poly(polygon.length,Color.BLACK);

		for( int i = 0; i < polygon.length; i++ )
			p.pts[i] = polygon[i].copy();



		SwingUtilities.invokeLater( new Runnable() {
			@Override
			public void run() {
				polygons.add( p );
			}
		});
	}

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		Graphics2D g2 = (Graphics2D)g;

		Point3D_F64 p1 = new Point3D_F64();
		Point3D_F64 p2 = new Point3D_F64();

		Point2D_F64 x1 = new Point2D_F64();
		Point2D_F64 x2 = new Point2D_F64();


		for( Poly poly : polygons ) {
			SePointOps_F64.transform(worldToCamera, poly.pts[0], p1);
			GeometryMath_F64.mult(K,p1,x1);

			// don't render what's behind the camera
			if( p1.z < 0 )
				continue;

			g2.setColor(poly.color);

			boolean skip = false;
			for( int i = 1; i < poly.pts.length; i++ ) {
				SePointOps_F64.transform(worldToCamera,poly.pts[i],p2);
				GeometryMath_F64.mult(K,p2,x2);

				if( p2.z < 0 ) {
					skip = true;
					break;
				}

				g2.drawLine((int)x1.x,(int)x1.y,(int)x2.x,(int)x2.y);

				Point3D_F64 tempP = p1;
				Point2D_F64 tempX = x1;

				p1=p2;p2=tempP;
				x1=x2;x2=tempX;
			}
			if( !skip ) {
				SePointOps_F64.transform(worldToCamera, poly.pts[0], p2);
				GeometryMath_F64.mult(K,p2,x2);
				g2.drawLine((int)x1.x,(int)x1.y,(int)x2.x,(int)x2.y);
			}
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
		Vector3D_F64 T = worldToCamera.getT();

		if( e.getKeyChar() == 'w' ) {
			T.z -= stepSize;
		} else if( e.getKeyChar() == 's' ) {
			T.z += stepSize;
		} else if( e.getKeyChar() == 'a' ) {
			T.x += stepSize;
		} else if( e.getKeyChar() == 'd' ) {
			T.x -= stepSize;
		} else if( e.getKeyChar() == 'q' ) {
			T.y -= stepSize;
		} else if( e.getKeyChar() == 'e' ) {
			T.y += stepSize;
		} else if( e.getKeyChar() == 'h' ) {
			worldToCamera.reset();
		}

		repaint();
	}

	@Override
	public void keyPressed(KeyEvent e) {}

	@Override
	public void keyReleased(KeyEvent e) {}

	@Override
	public void mouseClicked(MouseEvent e) {
		grabFocus();
	}

	@Override
	public void mousePressed(MouseEvent e) {
		prevX = e.getX();
		prevY = e.getY();

	}

	@Override
	public void mouseReleased(MouseEvent e) {}

	@Override
	public void mouseEntered(MouseEvent e) {}

	@Override
	public void mouseExited(MouseEvent e) {}

	@Override
	public void mouseDragged(MouseEvent e) {
		double rotX = 0;
		double rotY = 0;
		double rotZ = 0;

		rotY += (e.getX() - prevX)*0.01;
		rotX += (prevY - e.getY())*0.01;

		Se3_F64 rotTran = new Se3_F64();
		RotationMatrixGenerator.eulerXYZ(rotX,rotY,rotZ,rotTran.getR());
		Se3_F64 temp = worldToCamera.concat(rotTran,null);
		worldToCamera.set(temp);

		prevX = e.getX();
		prevY = e.getY();

		repaint();
	}

	@Override
	public void mouseMoved(MouseEvent e) {}

	private static class Poly
	{
		Point3D_F64[] pts;
		Color color;

		public Poly() {
		}

		public Poly(int length, Color color) {
			this.pts = new Point3D_F64[length];
			this.color = color;
		}
	}
}
